package com.workflow.engine.parser.flow.impl;

import com.workflow.engine.core.flow.IStep;
import com.workflow.engine.core.flow.StepState;
import com.workflow.engine.parser.annotation.AnnotationParser;
import com.workflow.engine.parser.constant.HttpMethod;
import com.workflow.engine.parser.misc.ILogger;
import com.workflow.engine.parser.strategy.ParamGeneratorConfig;
import com.workflow.engine.parser.strategy.ParamGeneratorUtil;
import com.workflow.engine.parser.utils.RequestUtil;
import com.workflow.engine.parser.utils.StepStateUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;

import java.util.List;
import java.util.Map;

import static com.workflow.engine.parser.utils.ParamsGeneratorStrategyUtil.generatorParamByStrategy;
import static com.workflow.engine.parser.utils.RequestUtil.get;
import static com.workflow.engine.parser.utils.RequestUtil.getAsStream;
import static com.workflow.engine.parser.utils.RequestUtil.post;

/**
 * Created by houjinxin on 2017/2/25.
 */
public abstract class AbstractStep implements IStep, ILogger {

    private boolean responseIsStream = false;

    public boolean isResponseIsStream() {
        return responseIsStream;
    }

    public void setResponseIsStream(boolean responseIsStream) {
        this.responseIsStream = responseIsStream;
    }

    /**
     * 发送前执行的函数
     */
    protected void beforeSendRequest(Map<String, Object> context){}

    protected String getQueryString(Map<String, Object> context){
        return "";
    }

    /**
     * 请求地址
     * @return
     */
    protected abstract String getRequestUrl();

    /**
     * HTTP 请求方法
     * @return
     */
    protected abstract HttpMethod getHttpMethod();

    /**
     * 设置请求头, 根据需求在子类中设置需要的请求头,若不需要可直接返回null
     *
     * @return
     */
    protected abstract Map<String, String> getHeaders();

    protected void printHeaders(Map<String, String> headers) {
        getLogger().info("Headers 总数：{}，详情如下：", headers.size());
        getLogger().info("======================================================================");
        for (Map.Entry<String, String> entry: headers.entrySet()) {
            getLogger().info("{}={}", entry.getKey(), entry.getValue());
        }
        getLogger().info("======================================================================");
    }

    protected void printCookies(){
        BasicCookieStore cookieStore = RequestUtil.getCookieStore();
        List<Cookie> cookies = cookieStore.getCookies();
        getLogger().info("Cookies 总数：{}，详情如下：", cookies.size());
        getLogger().info("======================================================================");
        for (int i = 0; i < cookies.size(); i++) {
            getLogger().info("{}", cookies.get(i));
        }
        getLogger().info("======================================================================");
    }

    protected void printRequestParameters(Map<String, String> requestParameters) {
        getLogger().info("RequestParameters 总数：{}，详情如下：", requestParameters.size());
        getLogger().info("======================================================================");
        for (Map.Entry<String, String> entry: requestParameters.entrySet()) {
            getLogger().info("{}={}", entry.getKey(), entry.getValue());
        }
        getLogger().info("======================================================================");
    }

    /**
     * 配置步骤所需参数列表,以及参数与参数获取方式的映射关系。
     *
     * @return
     */
    protected ParamGeneratorConfig stepNeededParamsConfig() {
        return null;
    }

    /**
     * 根据需要确定发送的请求是post还是get请求。结果类型定义为Object是因为要处理两种响应,一种是String的,一种是HttpResponse的
     * String类型的可以直接获取到响应内容,进行处理。
     * HttpResponse类型的需要判断下Content-Type,根据内容类型是否是json来决定后续流程
     *
     * @param requestUrl
     * @param requestParams
     * @param headers
     * @return
     * @throws Exception
     */
    protected Object sendRequest(String requestUrl, Map<String, String> requestParams, Map<String, String> headers) throws Exception {
        return sendRequest(requestUrl, requestParams, headers, false);
    }

    protected Object sendRequest(String requestUrl, Map<String, String> requestParams, Map<String, String> headers, boolean responseIsStream) throws Exception {
        if (getHttpMethod().equals(HttpMethod.GET)){
            if (responseIsStream) {
                return getAsStream(requestUrl, requestParams, headers);
            } else {
                return get(requestUrl, requestParams, headers);
            }
        } else if (getHttpMethod().equals(HttpMethod.POST)){
            if (responseIsStream) {
                return getAsStream(requestUrl, requestParams, headers);
            } else{
                return post(requestUrl, requestParams, headers);
            }
        } else {
            throw new RuntimeException("HTTP method [ " + getHttpMethod() + " ]为不支持的的HTTP请求方法");
        }
    }

    /**
     * 步骤执行前预先检查当前步骤是否需要执行或是直接跳过, 默认值当前步骤需要执行,故默认值是false。
     * 如交强险报价前,先检查交强险是否投保,若未投保则当前步骤直接跳过。故在交强险报价步骤中需要重写该方法。
     *
     * @param context
     * @return true-跳过, false-不跳过(默认)
     */
    protected boolean skipCurrentStepOrNot(Map<String, Object> context) {
        return false;
    }

    /**
     * 这个方法的目的是,解决不同的步骤中共用了许多相同的参数,造成为了构造请求参数,而产生大量的重复代码。因此如果在步骤中提供一个方法
     * stepNeededParamsMappings。在这里规定请求用到的所有参数(或者部分:对于商业险报价的请求参数可以通过追加到已经存在requestParams中来实现)
     * 以及参数的获取方式,参数获取策略等信息,那么就可以根据在上下文中已经注册的几种参数执行策略处理。
     * <p>
     * 支持通过注解的方式配置参数生成策略,且注解配置优先,当注解不存在时,才会调用stepNeededParamsConfig方法获取配置
     * 如果两种方案都没获取参数配置那么将抛出异常
     *
     * @param context
     * @param step
     * @return
     * @throws Exception
     */
    protected Map<String, String> getRequestParams(Map<String, Object> context, IStep step) throws Exception {
        ParamGeneratorConfig pgConfig;
        AnnotationParser parser = new AnnotationParser();
        pgConfig = parser.parserAnnotation(step.getClass());
        if (pgConfig.size() == 0) {
            pgConfig = stepNeededParamsConfig();
        }
        if (pgConfig == null) { //在方法中获取的pgConfig可能为空
//            throw new RuntimeException("参数生成方案配置无效,请检查步骤【" + step.getClass()
//                    .getSimpleName() + "】是否实现了stepNeededParamsConfig方法或配置有效的注解");
            pgConfig = ParamGeneratorUtil.configParamScheme();
        }
        return generatorParamByStrategy(context, pgConfig, step);
    }

    @Override
    public StepState run(Map<String, Object> context) throws Exception {
//        getLogger().debug("当前步骤【{}】", this.getClass().getName());
        if (skipCurrentStepOrNot(context)) {
            getLogger().info("跳过当前步骤【 {} 】", this.getClass().getSimpleName());
            return StepStateUtil.successfulStepState(this);
        } else {
            String requestUrl;
            String queryString = getQueryString(context);
            if (StringUtils.isBlank(queryString)) {
                requestUrl = getRequestUrl();
            } else {
                requestUrl = getRequestUrl() + "?" + queryString;
            }
            getLogger().info("请求URL为【 {} 】", requestUrl);
            Map<String, String> headers = getHeaders();
            getLogger().info("");
            printHeaders(headers);
            getLogger().info("");
            printCookies();
            getLogger().info("");
            Map<String, String> requestParams = getRequestParams(context, this);
            printRequestParameters(requestParams);
            getLogger().info("");
            beforeSendRequest(context);
            Object response;
            if (isResponseIsStream()) {
                response = sendRequest(requestUrl, requestParams, headers, true);
            } else {
                response = sendRequest(requestUrl, requestParams, headers);
            }
            return handleResponse(context, response);
        }
    }

    /**
     * 如果请求的结果是字符串类型的可以直接处理;如果请求是HttpResponse 要判定响应的结果是json格式的,
     * 若不是则终止流程并在日志中记录
     *
     * @param context
     * @param response
     * @return
     * @throws Exception
     */
    protected abstract StepState handleResponse(Map<String, Object> context, Object response) throws Exception;

}
